<template>
	<view class="content">
		<view class="container-image">
			<image class="content-image" src="/static/xhb.jpg"></image>
		</view>
		
		
		<view class="content-item">
			<view class="item" v-for="item in hotTopic" @click="onSendMessage(item)">
				<img class="item-icon" src="@/static/si.png">
				<span class="item-text">{{item}}</span>
			</view>
		</view>
		
		<scroll-view class="content-answer" scroll-y :scroll-into-view="lastMsgId" scroll-with-animation="true" scroll-animation-duration="300">
			<view 
				v-for="(msg, index) in messages"
				:key="index"
				:class="['message', msg.type]"
				:id="'msg' + index"
			>
				<!-- <view class="avatar">{{ msg.type === 'user' ? '👤' : '🤖' }}</view> -->
				<view class="avatar">
					<image v-if="msg.type === 'user'" src="/static/user.jpg"></image>
					<image v-else src="/static/bot.jpg"></image>
				</view>
				<view class="bubble">
          <template v-if="msg.content">
            <mp-html 
            :content="msg.content" 
            :markdown="true" 
            container-style="max-width:100%"
            />
          </template>
          <view v-else class="loading-placeholder">
            正在回答中
          </view>
        </view>
		  </view>
	</scroll-view>
		
		<view class="content-question">
			<view class="input-container">
				<input 
					class="message-input"
					v-model="messageText"
					placeholder="请输入您的问题"
					@confirm="sendMessage"
				/>
				<view class="send-btn" :class="{ loading: isStreaming }" @click.stop="isStreaming ? stopStreaming() : sendMessage()">
					<!-- <image v-if="!isStreaming" src="/static/logo.png" class="send-icon"/>-->
					<!-- <view v-if="!isStreaming" class="send-icon">👉</view> -->
					<image v-if="!isStreaming" src="/static/si.png" class="send-icon"/>
					<!-- <image v-else src="/static/1.png" class="stop-icon"/> -->
					<view v-else class="loading-icon"></view>
				</view>
			</view>
		</view>
		
	</view>
	
</template>

<script setup>
import {ref} from "vue"
import mpHtml from 'mp-html/dist/uni-app/components/mp-html/mp-html'
import {onLoad,onShow} from "@dcloudio/uni-app";



const hotTopic = ref([
	'黄冈市职工医疗保险参保缴费政策',
	'黄冈市城乡居民医疗保险参保缴费政策',
	'黄冈市医保异地就医转诊备案',
	'黄冈市职工医保退休免缴费政策',
	'黄冈市职工医保生育津贴申报办理指南',
	'黄冈市职工个人账户共济/医保钱包使用指南',
	'黄冈市医保门特慢病及特药门诊申报',
	'黄冈市各县市区医保部门咨询电话'
])

const messageText = ref('')
const messages = ref([])
const isStreaming = ref(false)
const abortController = ref(null)
const scrollTimer = ref(null)
const scrollTop = ref(0)


let tokenInfo = uni.getStorageSync('tokenInfo') || {}
let conversationId = ""
const assistantId = import.meta.env.VITE_ASSISTANT_ID
const grant_type = import.meta.env.VITE_GRANT_TYPE
const client_id = import.meta.env.VITE_CLIENT_ID
const client_secret = import.meta.env.VITE_CLIENT_SECRET
const app_id = import.meta.env.VITE_APP_ID
let history = []


let prevHeight = 0;

const scrollToBottom = () => {
  if (scrollTimer.value) clearTimeout(scrollTimer.value);
  scrollTimer.value = setTimeout(() => {
    uni.createSelectorQuery().select('.content-answer').boundingClientRect(res => {
      if (res) {
        uni.pageScrollTo({
          scrollTop: res.height,
          duration: 300,
          animation: true
        });
        prevHeight = res.height;
      }
    }).exec();
  }, 100);
};

const stopStreaming = () => {
  if (abortController.value) {
    abortController.value.abort();
    abortController.value = null;
    isStreaming.value = false;
  }
  clearInterval(streamInterval.value);
  streamInterval.value = null;
  if(scrollTimer.value) {
    clearTimeout(scrollTimer.value);
    scrollTimer.value = null;
  }
}

const streamInterval = ref(null)
const lastMsgId = ref('')

function sendMessage() {
  if (isStreaming.value){
    return
  }  
	if (messageText.value.trim()) {
		onSendMessage(messageText.value)
		messageText.value = ''
	}
}

// 消息更新方法
function updateBotMessage(id, content) {
  messages.value = messages.value.map(msg =>
    msg.id === id
      ? { ...msg, content: msg.content + content }
      : msg
  );
}



// 流式请求方法改造
async function onSendMessage(message) {
  if (isStreaming.value) return;

  const userMsg = {
    type: 'user',
    content: message,
    id: 'usr_' + Date.now() + '_' + Math.floor(Math.random() * 1000)
  };
  messages.value = [...messages.value, userMsg];
  const botMsg = {
    type: 'bot',
    content: '',
    id: 'bot_' + Date.now() + '_' + Math.floor(Math.random() * 1000)
  };
  messages.value = [...messages.value, botMsg];
  try {
    isStreaming.value = true;
    abortController.value = new AbortController();
    const response = await fetch('/web/openapi/v2/assistant/chat/completions', {
      method: 'POST',
      headers: {
        'Authorization': `Bearer ${uni.getStorageSync('tokenInfo').access_token}`,
        'Content-Type': 'application/json'
        
      },
      body: JSON.stringify({
        stream: true,
        conversationId: conversationId,
        assistantId: assistantId,
        prompt: message,
		history:history,
		use_search:true,
		model:"deepseek-v3"
      }),
      signal: abortController.value.signal
    });

    // 流式读取机制核心实现
    // 1. 获取可读流读取器，用于分块读取数据
    const reader = response.body.getReader();
    // 2. 初始化文本解码器，处理二进制流到字符串的转换
    const decoder = new TextDecoder();
    // 3. 创建缓冲区存储未处理数据（缓冲区管理策略）
    let buffer = '';

    // 流式数据读取循环（核心逻辑）
    while (true) {
      // 4. 异步读取数据块
      const { done, value } = await reader.read();
      // 5. 流结束处理
      if (done) {
        // 处理缓冲区残留数据
        if (buffer) {
          processBuffer(buffer, decoder, botMsg.id);
        }
        break;
      }
      
      // 6. 解码并追加到缓冲区（使用stream:true保持解码状态）
      buffer += decoder.decode(value, { stream: true });
      // 7. 处理当前缓冲区数据并返回未处理部分
      buffer = processBuffer(buffer, decoder, botMsg.id);
    }
  } catch(error) {
    if (error.name !== 'AbortError') {
      console.error('流式请求异常:', error);
      updateBotMessage(botMsg.id, '请求异常，请重试');
    }
    isStreaming.value = false;
  }
}

// 流式数据处理核心函数
function processBuffer(buffer, decoder, msgId) {
	
  // 1. 分割缓冲区数据为独立行（支持\n和\r分隔符）
  const lines = buffer.split(/\n|\r/);
  // JSON数据处理流程
  lines.forEach(line => {
    if (line.startsWith('data:')) {
      const jsonStr = line.slice(5);
      
      // 2. 处理流结束标记
      if(jsonStr === '[DONE]') {
        isStreaming.value = false;
        return;
      }

      try {
        // 3. 前置JSON格式验证
        if(!isValidJSON(jsonStr)) return;
        
        // 4. 解析JSON数据
        const data = JSON.parse(jsonStr);
       
        // 5. 提取有效内容（安全访问嵌套属性）
        const content = data.response;
		if (data.history.length){
			history = data.history
		}
        if (data.finish) {
          isStreaming.value = false;
          return;
        }
        
        // 6. 更新消息列表（保持响应式）
        messages.value = [...messages.value];
        updateBotMessage(msgId, content);
        
        // 7. 自动滚动到底部
        lastMsgId.value = msgId;
        setTimeout(scrollToBottom, 500);
      } catch(e) {
        // 错误处理机制
        console.error('JSON解析错误:', e);
        isStreaming.value = false;
      }
    }
  });
  
  // 8. 返回未处理的行数据（保持缓冲区连续性）
  return lines.pop() || '';
}

// JSON格式验证函数
function isValidJSON(str) {
  try {
    // 双重验证策略：1. 解析验证 2. 结构验证
    JSON.parse(str);
    return true;
  } catch {
    // 捕获异常并返回验证失败
    return false;
  }
}



// 会话id--uuid
function generateUUID() {
  const buffer = new Uint8Array(16);
  crypto.getRandomValues(buffer);
  
  buffer[6] = (buffer[6] & 0x0f) | 0x40;
  buffer[8] = (buffer[8] & 0x3f) | 0x80;
  
  const hex = Array.from(buffer)
    .map(byte => byte.toString(16).padStart(2, '0'))
    .join('');
  
  return `${hex.substr(0,8)}-${hex.substr(8,4)}-${hex.substr(12,4)}-${hex.substr(16,4)}-${hex.substr(20,12)}`;
}




onLoad(()=>{
  // 页面加载时生成token
  if (!tokenInfo.access_token || tokenInfo.expires_at < Date.now()) {
  	uni.request({
  		url: `/web/openapi/service/v1/oauth/${app_id}/token`,
			method: 'POST',
			header: {
				'content-type': 'application/json'
			},
			data: {
				"grant_type":grant_type,
				"client_id":client_id,
				"client_secret":client_secret
			},
      success: (res) => {
				let newTokenInfo = {
					access_token: res.data.data.access_token,
					expires_at: Date.now() + res.data.data.expires_in * 1000,
				}
				uni.setStorageSync('tokenInfo', newTokenInfo)
        tokenInfo = newTokenInfo
			},
			fail: (err) => {
				console.log('请求失败:', err);
			}
  	})
  }
	// 页面加载时生成会话id
	conversationId = generateUUID();
})


</script>

<style lang="scss" scoped>

.container-image {
  padding: 5px;
  box-sizing: border-box;
}

.content-image {
  width: 100%;
  height: 20vh;
  object-fit: contain;
}

.content {
  width: 100%;
  box-sizing: border-box;
  padding: 0 6px;
  padding-bottom: 80px;
  background-color: #ffffef;
}

.content-item {
  width: 100%;
  box-sizing: border-box;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  padding: 10px;
}

.item {
  flex: 0 0 calc(50% - 6px);
  box-sizing: border-box;
  min-width: 0;
  background: #f8f9fa;
  border-radius: 8px;
  padding: 14px 16px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-size: 10px;
  color: #2c3e50;
  box-shadow: 0 2px 4px rgba(0,0,0,0.05);
  transition: all 0.2s ease;
}

.item-icon {
  width: 16px;
  height: 16px;
  object-fit: contain;
  vertical-align: middle;
}

.item-text {
  flex: 1;
  text-align: center;
}

.content-question {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #fff;
  z-index: 100;
  padding-bottom: env(safe-area-inset-bottom);
  box-shadow: 0 -2px 12px rgba(0,0,0,0.05);
}

.input-container {
  display: flex;
  align-items: center;
  padding: 12px 16px;
  gap: 8px;
}

.message-input {
  flex: 1;
  height: 40px;
  padding: 10px 16px;
  box-sizing: border-box;
}

.content {
  padding-bottom: 88px;
}

.content-answer {
  flex: 1;
  padding: 12px 16px;
  overflow-y: auto;
}

.message {
  display: flex;
  margin: 12px 0;
  min-height: 40px;
}

.message.bot {
  flex-direction: row;
}

.message.user {
  flex-direction: row-reverse;
}

.avatar {
  width: 26px;
  height: 26px;
  border-radius: 50%;
  background: #f0f1f2;
  overflow: hidden;
}

.avatar image {
  width: 100%;
  height: 100%;
  object-fit: contain;
}

.bubble {
  max-width: 70%;
  padding: 12px 16px;
  border-radius: 16px;
  line-height: 1.5;
  position: relative;
  word-break: break-word;
}
.message.user .avatar {
  margin-right: 20px;
}
.message.bot .bubble {
  background: #f0f1f2;
  margin-left: 8px;
}

.message.user .bubble {
  background: #46c8ff;
  color: white;
  margin-right: 8px;
}

.message.user .bubble::after,
.message.bot .bubble::before {
  content: '';
  position: absolute;
  width: 0;
  height: 0;
  border-style: solid;
  top: 12px;
}

.message.user .bubble::after {
  right: -8px;
  border-width: 8px 0 8px 8px;
  border-color: transparent transparent transparent #46c8ff;
}

.message.bot .bubble::before {
  left: -8px;
  border-width: 8px 8px 8px 0;
  border-color: transparent #f0f1f2 transparent transparent;
}



.send-btn {
  width: 40px;
  height: 40px;
  border-radius: 50%;
  background: #fff6d9;
  display: flex;
  align-items: center;
  justify-content: center;
  /* 移除可能存在的定位属性 */
  position: static;
}

.send-icon {
  width: 20px;
  height: 20px;
}

.loading-icon {
  width: 20px;
  height: 20px;
  border: 2px solid #ffb700;
  border-radius: 50%;
  border-top-color: transparent;
  animation: spin 0.8s linear infinite;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}

.item:hover {
  background: #e9ecef;
  transform: translateY(-2px);
  box-shadow: 0 4px 8px rgba(0,0,0,0.1);
}
.loading-placeholder {
  position: relative;
  padding-right: 1em;
}

.loading-placeholder::after {
  content: '...';
  display: inline-block;
  width: 0;
  overflow: hidden;
  vertical-align: bottom;
  white-space: nowrap;
  animation: dots 1.5s infinite;
}

@keyframes dots {
  0% { width: 0; }
  25% { width: 1ch; }
  50% { width: 2ch; }
  75% { width: 3ch; }
  100% { width: 0; }
}
</style>


